//import chivox from '../chivox';
import Config from '../config.json';
import Html5Player from './html5player'
import Utils from '../utils'
import { get_sig } from '../public';
//import { webWorker } from '../worker/webWorker-mp3'
import { webWorker } from '../worker/webWorker'
import  VolumeBar  from './volumebar'

const default_record_option = {
    appKey:"",
    //server :(document.location.protocol == "https:" ? "wss://" : "ws://") + Config.server,
    server: "wss://" + Config.server,
    alg:"sha1",
    recordId:"chivox-recorder",
    onInit:function (mess) {

    },
    onError: function (err) {

    },
    onConnectorStatusChange:(code)=>{

    }
};

class Html5Recorder {
    constructor(options){
        let that = this;
        this._status = Utils.Status.initing;
        if(!Utils.support_h5()){
            return;
        }
        this.options = Utils.extend(options,default_record_option,true);
        if(this._isOptions(this.options)){
            return;
        }
        this.options.server = this.options.server && this.options.server.substr(0,3) === "wss" ?  this.options.server : default_record_option.server;
        this.Utils = Utils;
        this.__curr_record_param = null;
        this.__cnt = 0;
        this.options.tokenId = null;
        this.player = new Html5Player({});
        this.__audio_replay = document.createElement('audio');
        this.__on_replay_stop = null;
        this.__on_replay_start = null;
        this._audio_sampleRate = null;
        this.wsContext = {};

        if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
            navigator.mediaDevices.getUserMedia({audio:true,video:false}).then((stream) =>{
                that._gotStream(stream)
            }).catch((e) => {
                if(typeof options.onError == "function"){
                    console.error("{id:50003,message:no audio device}");
                    options.onError({id:50003,message:"no audio device"});
                }
                console.error(e);
            })
        }else {
            navigator.getUserMedia({audio:true},function (stream) {
                that._gotStream(stream);
            },function (stream) {
                if(typeof options.onError == "function"){
                    console.error(`{id:50003,message:no audio device}`);
                    options.onError({id:50003,message:"no audio device"});
                }
                console.error(stream);
            })
        }
        this.getSig = typeof this.options.signature === "function" ? this.options.signature  : get_sig;
        this.test_connection();
    }

    /**
     *  初始化引擎函数
     *  1.创建音频上下文
     *  2.开启录音机
     *  3.绘制波形图
     *  4.初始化web worker用于异步处理音频
     *  5.录音机上下文记录录音机状态
    * */

    _gotStream(stream){
        let AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
        this.context = new AudioContext();
        let nodeNum = 0;

        let bufferLen = Utils.buff_size();
        let outputArray = new Int8Array(258);
        let realAudioInput = this.context.createMediaStreamSource(stream);

        this.inputPoint = this.context.createGain();
        this.audioInput = realAudioInput;
        this.audioInput.connect(this.inputPoint);

        this.analyserNode = this.context.createAnalyser();
        this.analyserNode.fftSize = 2048;
        this.inputPoint.connect(this.analyserNode);
        this.__volumebar = new VolumeBar(`#${this.options.recordId}`, this.context, this.analyserNode, realAudioInput);

        this.node = this.context.createScriptProcessor(bufferLen, 1, 1);

        let blob_record_worker = new Blob([webWorker], { type: 'text/plain' });

        this.record_worker = new Worker(window.URL.createObjectURL(blob_record_worker));
        this.record_worker.onmessage = (e) => {
            if (e.data.command == "debug") {} else if (e.data.command == "blob") {
                if (!this.__audio_replay){
                    this.__audio_replay = document.createElement('audio');
                }
                try {
                    // Fallback if createObjectURL is not supported
                    if(e.data.blob.size != 0){
                        var fileReader = new FileReader();
                        fileReader.onload = (event) => {
                            this.__audio_replay.src = event.target.result;
                        };
                            fileReader.readAsDataURL(e.data.blob);
                       
                    }else{
                        this.__audio_replay.src = null;
                    }

                } catch (err) {
                    console.warn(err);
                }

                this.__audio_replay.type = e.data.blob.type ? e.data.blob.type :'audio/mp3';
                this.__audio_replay.loop = false;
                this.__audio_replay.addEventListener("ended", () => {
                    if (this.__on_replay_stop)
                        this.__on_replay_stop();
                }, false);
                this.__audio_replay.onerror = (e) => {
                    if (typeof this.__on_replay_stop === "function")
                        this.__on_replay_stop();
                };
                this.__audio_replay.onplay = (e) => {
                    if(typeof this.__on_replay_start == "function"){
                        this.__on_replay_start();
                    }
                }
            } else {

                let buffer = e.data.buffer;

                if(e.data.dataType == 'wav'){
                    if(this.wsContext[`ws${this._tokenId}`] && this.wsContext[`ws${this._tokenId}`].msgQue){
                        this.wsContext[`ws${this._tokenId}`].msgQue.push({cmd:"send",data:buffer});
                    }else{
                        console.error("this.wsContext[`ws${this._tokenId}`].msgQue not found");
                    }

                    if(typeof this.wsContext[`ws${this._tokenId}`].sendAudio === "function"){
                        this.wsContext[`ws${this._tokenId}`].sendAudio();
                    }else{
                        console.error("sendAudio not found")
                    }
                }else{
                    if(this.wsContext[`ws${this._tokenId}`] && this.wsContext[`ws${this._tokenId}`].msgQue){
                        this.wsContext[`ws${this._tokenId}`].msgQue.push({cmd:"send",data:buffer});
                    }else{
                        console.error("this.wsContext[`ws${this._tokenId}`].msgQue not found");
                    }

                    if(typeof this.wsContext[`ws${this._tokenId}`].sendAudio === "function"){
                        this.wsContext[`ws${this._tokenId}`].sendAudio();
                    }else{
                        console.error("sendAudio not found")
                    }
                }

                if(typeof this._recorder_frame === "function"){
                    this._recorder_frame({buffer: buffer,isLastFrame: false});
                }
            }
        };

        this.record_worker.postMessage({
            command: 'init',
            config: {
                sampleRate: this.context.sampleRate,
                outputBufferLength: bufferLen
            }
        });

        this.node.onaudioprocess = (e) => {
            // 获得缓冲区的输入音频，转换为包含了PCM通道数据的32位浮点数组
            let buffer = e.inputBuffer.getChannelData(0);
            // 获取缓冲区中最大的音量值
            let maxVal = Math.max(...buffer);

            if(this.options.micWatch === true){
                if(maxVal === 0){
                    nodeNum ++;
                }
                if(nodeNum === 10){
                    if(this._status === Utils.Status.recording){
                        this.stopRecord();
                    }
                   if(this.__timer_stop){
                       clearTimeout(this.__timer_stop);
                       this.__timer_stop = null;
                   }
                    if(typeof this.options.onError == "function"){
                        console.error(`{id:50003,message:no audio device}`);
                        this.options.onError({id:50003,message:"no audio device"});
                    }
                }
            }

            if (this._status === Utils.Status.recording && maxVal != 0) {
                this.record_worker.postMessage({
                    command: 'record',
                    audioType: this._audio_sampleRate.audioType,
                    buffer: buffer
                });
            }
        };
        this.inputPoint.connect(this.node);
        this.node.connect(this.context.destination);
        this._resetStatus();
        if(typeof this.options.onInit == "function"){
            this.options.onInit("success");
        }
    }

    /**
     * 开始录音。
     *
     * @param {Object} params - 录音时所需的参数。参数有：
     * @param {string} params.duration - 录音最大时长，单位：毫秒
     * @param {boolean} params.playDing - 录音前是否播放"ding"
     * @param {Object} params.serverParams - 录音参数。详见:{}
     * @param {Callback} params.onRecordIdGenerated - 开始录音后生成recordId后的Callback。格式：(tokenId) => { var tokenId = tokenId.tokenId; }
     * @param {Callback} params.onStart - 开始录音后的Callback。格式：() => { }
     * @param {Callback} params.onStop - 录音结束后的Callback。格式：() => { }
     * @param {Callback} params.onInternalScore - 录音中服务器Push回来的中间评分结果的Callback。格式：(code, message) => { }
     * @param {Callback} params.onScore - 服务器评分结果返回后的Callback。格式：(data) => { }
     * @param {Callback} params.onScoreError - 服务器评分出错返回后的Callback。格式：(error) => { }
     *
     */

    record(params){
        let that = this;
        //判断是否初始化成功
        if(that._status === Utils.Status.initing){
            if (typeof that.options.onError == "function"){
                that.options.onError({
                    id:70001,
                    message:'must recorder init success first'
                });
            }
            console.error("{id:70001,message:'must recorder init success first'}");
            return;
        }
        //连接数不能大于4个
        if(Object.keys(this.wsContext).length >=4){
            console.error("{id:71001, message:'Websocket connections cannot be more than 4'}");
            this.options.onError({
                id:71001,
                message:"Websocket connections cannot be more than 4"
            });
            return
        }
        //阻止重复点击 判断上一次录音是否结束
        if(that._status !== Utils.Status.completed){
           if (typeof that.options.onError == "function"){
               that.options.onError({
                   id:70002,
                   message:'please wait the last record end!'
               });
           }
            console.error("{id:70002, message:'please wait the last record end!'}");
            return;
        }

        if (!this.record_worker) {
            if(typeof that.options.onError == "function"){
                that.options.onError({
                    id:72001,
                    message:'no input device!'
                });
                console.error("{id:72001,message:'no input device!'}");

            }
            return;
        }
        that._status = Utils.Status.starting;
        console.info("recorder starting");
        const currentTime = Math.floor(new Date().getTime());
        if (this.options.sig !== '' && this.options.timestamp !== undefined && currentTime - parseInt(this.options.timestamp) <= (Config.timeout*1000)) {
            that.__startRecord(params);
        } else {
            let sigData = this.getSig();
            const invalid_sidData = this._isInvalidSigData(sigData,this.options);
            if(invalid_sidData){
               this.options.sig = sigData.sig;
                this.options.timestamp = sigData.timestamp;
                that.__startRecord(params);
            }else{
                that._resetStatus();
            }
        }
    }

    /**
     * 开始录音。
     *
     * @param {Object} params - 录音时所需的参数。参数有：
     *
     */

    __startRecord(params){
        const that = this;
        let prev_param = this.__curr_record_param;
        params.serverParams = params.serverParams && typeof params.serverParams == "object" ? params.serverParams : {};
        params.serverParams.sig = this.options.sig;
        params.serverParams.timestamp = this.options.timestamp;
        params.playDing = params.hasOwnProperty("playDing") && typeof params.playDing === "boolean" ? params.playDing : true;
        params.audioType = params.audioType == 'mp3'  ? 'mp3' : 'wav';
        params.sampleRate =  params.audioType == 'mp3' ? '44100' : '16000';
        params.logbus = typeof params.logbus === "boolean" ? params.logbus : true;
        const coreTimeout = params.serverParams.coreTimeout;
        params.serverParams.coreTimeout = typeof coreTimeout == "number" ? coreTimeout : Config.core_timeout;
        params.duration = this._getDuration(params);
        this.__curr_record_param = params;
        this._audio_sampleRate = {audioType:params.audioType,sampleRate:params.sampleRate};
        this.options.data = params;
        this._tokenId = Utils.uuid();
        this.wsContext[`ws${that._tokenId}`] = {
            tokenId:that._tokenId,
            websocket:null,
            url: null,
            connectStatus:Utils.ConnectStatus.initing, //当前websocket连接状态
            msgQue:[], //当前评分消息队列
            sendAudio:null  //发送音频队列
        };

        this.wsContext[`ws${that._tokenId}`].sendAudio = this._feed_audio(this.wsContext[`ws${that._tokenId}`]);
        this._status = Utils.Status.connecting;
        if (!prev_param ||  this.wsContext[`ws${that._tokenId}`].websocket == null){
            console.info('[connectWebSocket]:connect websocket start!!');
            this.wsContext[`ws${that._tokenId}`].connectStatus = Utils.ConnectStatus.connecting;
            this.__connectWebSocket(params);
        }

        if (params.playDing) {
            const isFireFox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1 ? true : false;
            
           if (this.player.can_play && !isFireFox) {
                this.player.load({
                    url: `${Config.host}/static/ding.mp3`,
                    success: (code, message) => {
                        this.player.play({
                            position: 0,
                            onStop: (code, message) => {
                                if(this._status == Utils.Status.connecting){
                                    this._status = Utils.Status.recording;
                                    params.onStart();
                                    this._startCommand(params);
                                    console.info('[startRecord]: start recording!!');
                                }
                            }
                        });
                    },
                    error:function (err) {

                    }
                });
            }else{
               let audio_play_ding = document.createElement('audio');
               audio_play_ding.src = `${Config.host}/static/ding.mp3`;
               audio_play_ding.addEventListener("canplaythrough",function () {
                   audio_play_ding.play();
               },false);
               audio_play_ding.addEventListener("ended", () => {
                   if(that._status == Utils.Status.connecting){
                       that._status = Utils.Status.recording;
                       params.onStart();
                       that._startCommand(params);
                       console.info('[startRecord]: start recording!!');
                   }
                   audio_play_ding = null;
               }, false);
           }
        } else {
            if(this._status == Utils.Status.connecting){
                this._status = Utils.Status.recording;
                params.onStart();
                this._startCommand(params);
                console.info('[startRecord]: start recording!!');
            }
        }
    }

    /**
     * 启动录音引擎 创建自动录音停止函数
     *
     *
     * */
    _startCommand(params){
        this.record_worker.postMessage({
            command: 'reset'
        });
        this.__timer_stop= setTimeout(() => {
            this.stopRecord(false);
        }, parseInt(params.duration));
    }

    /**
    *  连接webSocket函数
    *  IE浏览器webSocket 最大连数限制为6个(SDK不支持IE浏览器，故不需要考虑),其他浏览器设置为10个，SDK限制最大连接数为4个;
    *  每次请求建立独立webSocket连接,该连接暂时不复用
     *  创建websocket连接回调，并对websocket对应连接状态进行处理
     *  wsContext[`ws${tokenId}`].connectStatus = {
     *      starting,
     *      waitResult,
     *      completed
     *  }
    * */

    __connectWebSocket(params){
        const that = this;
        let wsContextN = this.wsContext[`ws${that._tokenId}`];
        let url = `${this.options.server}/${params.serverParams.coreType}`;
        if (params.serverParams.res){
            url += `/${params.serverParams.res}`;
        }
        url += '?e=0&t=0&version=2';
        wsContextN.url = url;

        this.__log({evtid: 1, statusid: 19, serverurl:url, reason: '', logbus: params.logbus});
        wsContextN.websocket = new WebSocket(url);
        wsContextN.websocket.onopen = this._wsOnOpen(wsContextN,params);
        wsContextN.websocket.onclose = this._wsOnClose(wsContextN,params);
        wsContextN.websocket.onmessage = this._wsOnMessage(wsContextN);
        wsContextN.websocket.onerror = this._wsOnError(wsContextN,params);
    }

    /**
     *  websocket onopen 回调函数
     *  回调函数采用闭包形式 每个连接对应一个连接状态管理
     *  websocket 连接成功回调--- 发送录音引擎验证信息，当验证通过方可进行评分
    * */
    _wsOnOpen (wsParams,params){
        const that = this;
        const self_tokenId = wsParams.tokenId;
        const url = wsParams.url;
        const logbus = params.logbus;
        return (e)=>{
            if(that.wsContext[`ws${self_tokenId}`] && that.wsContext[`ws${self_tokenId}`].websocket.readyState == WebSocket.OPEN  ){
                let _cmd_start = that.__json_param(0, params);
                that.wsContext[`ws${self_tokenId}`].websocket.send(_cmd_start);

                that.wsContext[`ws${self_tokenId}`].websocket.send(that.__json_param(1, params));
                that.wsContext[`ws${self_tokenId}`].connectStatus = Utils.ConnectStatus.connectSuccess;
                that.wsContext[`ws${self_tokenId}`].sendAudio();
                console.info('[connectWebSocket]:connect websocket success!!');

                that.__log({evtid: 1, statusid: 15, serverurl:url, reason: '', logbus: logbus});
            }
        }
    }

    /**
     * websocket 连接关闭回调函数
     * 当录音机在非 initing 和 非 completed 状态 websocket.onclose 函数执行代表SDK连接出现错误
     *
     * */
    _wsOnClose (wsParams,params){
        const that = this;
        const url = wsParams.url;
        const logbus = params.logbus;
        return (e)=>{
            if(typeof that.options.onError == "function" && (that.wsContext[`ws${wsParams.tokenId}`]&&that.wsContext[`ws${wsParams.tokenId}`].connectStatus != Utils.ConnectStatus.connectEnd)){
                that.options.onError({
                    id:73001,
                    tokenId:wsParams.tokenId,
                    message:`(wsOnClose)WebSocket connection to '${url}' failed.`
                });
                console.error(`{id:73001, tokenId:${wsParams.tokenId},message:(wsOnClose)WebSocket connection to ${url} failed. }`);
                that._resetFlag(true,wsParams.tokenId);
                that._resetStatus();
            }

            that.__log({evtid: 1, statusid: 17, serverurl:url, reason: '', logbus: logbus});
        }
    }

    /**
     * websocket onmessage回调闭包处理
     *
     * 评分服务器返回所有信息（包括评分参数验证，评分（含边录边评）信息，error信息）回调
     **/
    _wsOnMessage(wsParams){
        const that = this;
        const self_tokenId = wsParams.tokenId;
        let wsMessageCon = that.wsContext[`ws${self_tokenId}`];
        return (e)=>{
            let json = JSON.parse(e.data);
            console.log(json);
            if(json.error || json.eof == 1){
                wsMessageCon.connectStatus = Utils.ConnectStatus.connectEnd;
                if (wsMessageCon.timer_wait_res){
                    clearTimeout(that.wsContext[`ws${self_tokenId}`].timer_wait_res);
                }
                if(wsMessageCon.websocket){
                    wsMessageCon.websocket.close();
                    wsMessageCon.websocket = null;
                    wsMessageCon.msgQue = [];
                    wsMessageCon = null;
                    delete that.wsContext[`ws${self_tokenId}`];
                }
            }

            Utils.log('[ onmessage ]: ' + e.data);

            if (json.error) {
                if(that._status == Utils.Status.completed){}else{
                    if(that._status !== Utils.Status.recording){
                        that._resetStatus();
                        if(that._status == Utils.Status.connecting){
                            that._status = Utils.Status.connectFalse;
                        }
                        if(typeof  that.__curr_record_param.onStop == "function"){
                            that.__curr_record_param.onStop();
                        }
                    }
                    that.stopRecord(true);
                }
                let err = JSON.stringify(json.error);

                if (json.error.id && that.__curr_record_param && typeof that.__curr_record_param.onScoreError == "function") {
                    json.tokenId = json.tokenId ? json.tokenId : self_tokenId;
                    that.__curr_record_param.onScoreError(json);
                } else {
                    that.options.onError({id:json.error.id,tokenId:json.tokenId,message:json});
                    console.error(err);
                }
            } else {

                if (json.eof == 0) {
                    if (that.__curr_record_param && that.__curr_record_param.onInternalScore) {
                        that.__curr_record_param.onInternalScore(json);
                    } else {
                        console.error('no onInternalScore callback.', json);
                    }
                } else {
                    if (that.__curr_record_param && this.__curr_record_param.onScore) {
                        that.__curr_record_param.onScore(json);
                    } else {
                        console.error('no onScore callback.', json);
                    }
                }
            }
        }
    }

    /**
     *  websocket onerror回调闭包处理
     *  当websocket连接执行该回调函数，代表websocket开始连接服务器失败（）
     *
     * * */

    _wsOnError(wsParams,params){
        const that = this;
        const url = wsParams.url;
        const logbus = params.logbus;
        return (e)=>{
           // that.stopRecord(true);
            that._resetStatus();
            if(typeof that.options.onError == "function"){
                that.options.onError({
                    id:73001,
                    tokenId:wsParams.tokenId,
                    message:`(wsOnError)WebSocket connection to '${url}' failed.`
                });
            }
            console.error(`{id:73001, tokenId:${wsParams.tokenId},message:(wsOnError)WebSocket connection to ${url} failed. }`);
            that._resetFlag(true,wsParams.tokenId);

            that.__log({evtid: 1, statusid: 16, serverurl:url, reason: e.data, logbus: logbus});
        }
    }

    /* *
      * SDK处理录音相关数据函数
      *  0 => connect（录音服务验证信息） ,
      *  1 => start（开始录音评分信息） ,
      *  2 => end
      * */
    __json_param(type, params) {
        var json = null;

        if (type === 0) {
            json = {
                sdk: {
                    version: 0x01000000,
                    source: 0x04,
                    protocol: 'websocket'
                },
                app: {
                    applicationId: this.options.appKey,
                    sig: params.serverParams.sig,
                    timestamp: params.serverParams.timestamp,
                    userId: params.serverParams.userId,
                    alg: this.options.alg
                }
            };
        } else if (type === 1) {
            json = {
                tokenId: this._tokenId,
                audio: {
                    audioType: params.audioType,
                    channel: 1,
                    sampleRate: params.sampleRate,
                    sampleBytes: 2
                },
                request: params.serverParams
            };
            if(typeof this.__curr_record_param.onRecordIdGenerated == "function"){
                this.__curr_record_param.onRecordIdGenerated({
                    tokenId: this._tokenId
                });
            }
        }

        return JSON.stringify(json);
    }

    /**
     *  发送录音数据函数
     *
     *  state == 1 ,录音结束发送空音频
     * */
    _send_audio(state, data) {
        const that = this;
        this.__cnt++; //send data when __cnt == 6

        if (1 != state)
            this.__buf_rec.push(data);

        if (1 == state || 6 >= this.__cnt) {
            var outputArray;

            if (this.__buf_rec.length > 0) {
                var output = this.__buf_rec.splice(0, this.__buf_rec.length);
                var i;
                outputArray = new Int16Array(output.length * 320);
                for (i = 0; i < output.length; i++)
                    outputArray.set(output[i], i * 70);

                if (this.wsContext[`ws${that._tokenId}`]&&this.wsContext[`ws${that._tokenId}`].websocket && this.wsContext[`ws${that._tokenId}`].websocket.readyState == WebSocket.OPEN) {
                    console.debug("send buffer start");
                    this.wsContext[`ws${that._tokenId}`].websocket.send(outputArray.buffer);
                    console.debug(outputArray.buffer);
                } else {
                    console.debug("send fail cache buffer!!!!");
                    this.__buf_ws.push(outputArray.buffer);
                }
            }

            this.__cnt = 0;
            if (state == 1) {
                if (this.wsContext[`ws${that._tokenId}`]&&this.wsContext[`ws${that._tokenId}`].websocket &&this.wsContext[`ws${that._tokenId}`].websocket.readyState == WebSocket.OPEN) {
                    this.wsContext[`ws${that._tokenId}`].websocket.send(new ArrayBuffer(0));
                }
            }
        }
    }

    send_audio(params) {
        const that = this;

                if (state == 0 &&data.length > 0) {

                    if (this.wsContext[`ws${that._tokenId}`]&&this.wsContext[`ws${that._tokenId}`].websocket && this.wsContext[`ws${that._tokenId}`].websocket.readyState == WebSocket.OPEN) {
                        console.debug("send buffer start");
                        this.wsContext[`ws${that._tokenId}`].websocket.send(data.buffer);
                        console.debug(data.buffer);
                    } else {
                        this.__buf_ws.push(data.buffer);
                    }
                }
                if (state == 1) {
                    if (this.wsContext[`ws${that._tokenId}`]&&this.wsContext[`ws${that._tokenId}`].websocket &&this.wsContext[`ws${that._tokenId}`].websocket.readyState == WebSocket.OPEN) {
                        this.wsContext[`ws${that._tokenId}`].websocket.send(new ArrayBuffer(0));
                    }else{
                        if (that.wsContext[`ws${that._tokenId}`]&&that.wsContext[`ws${that._tokenId}`].websocket &&that.wsContext[`ws${that._tokenId}`].websocket.readyState == WebSocket.OPEN) {
                            this.wsContext[`ws${that._tokenId}`].websocket.send(new ArrayBuffer(0));
                        }
                    }
                }

    }

    _feed_audio(params){
        let that = this;
        return function () {

            if(!params.msgQue || params.msgQue.length == 0){
                console.debug("[_feed_audio]: feed audio end or msgQue empty");
                return
            }
            
            if(params.connectStatus == Utils.ConnectStatus.connecting){
                setTimeout(()=>{
                    params.sendAudio();
                },10);
                return;
            }

            if(params.connectStatus !== Utils.ConnectStatus.connectSuccess && params.connectStatus != Utils.ConnectStatus.waitResult ||  (params && params.websocket.readyState != WebSocket.OPEN)){
                console.debug(`[_feed_audio]:params.websocket.readyState:${params.websocket.readyState}`);
                return;
            }
            
            let msg = params.msgQue[0];
            params.msgQue.splice(0,1);

            if(msg.cmd == "send"){
                params.websocket.send(msg.data.buffer);
            }

            if(msg.cmd == "stop"){
                params.websocket.send(new ArrayBuffer(0));
            }

            if(params.msgQue && params.msgQue.length > 0){
                setTimeout(()=>{
                    params.sendAudio();
                },10);
            }
        }
    }

    /**
     * 实时返回音频功能
     * params- ArrayBuffer
     * */
    _recorder_frame(frame){

        frame = typeof frame === "object" ? frame : {};

        let self_params = this.__curr_record_param;

        let data = null;
        let buffer = frame.buffer;
        let isArrayBuffer = ArrayBuffer.isView(buffer);

        if(this._status === Utils.Status.recording && frame.isLastFrame === false && isArrayBuffer){
            data = {frameBuffer: buffer,isLastFrame: false};
        }

        if(this._status === Utils.Status.completed && frame.isLastFrame === true){
            data = {frameBuffer:  new ArrayBuffer(0),isLastFrame: true};
        }

        if(typeof self_params.onFrame === "function"){
            self_params.onFrame(data)
        }
    }

    /**
    * 计算录音时长函数
    *
    * @return {number} - duration 录音时长,单位(ms)
    *
    * */
    _getDuration(params){
        let duration = 2030;
        let core_type = params.serverParams.coreType;
        if (params.duration && typeof params.duration == "number"){
            duration = params.duration >=2000 ? params.duration + 30 : duration;
        }else{
            if (Utils.in_array(core_type, ['cn.word.score', 'cn.sent.score'])) {
                let reftxt_cnt = params.serverParams.refText.replace(/^\s+|\s+$/g, '').split('-').length;
                duration = 2030 + reftxt_cnt * 600;
            } else if (Utils.in_array(core_type, ['en.word.score', 'en.sent.score'])) {
                let reftxt_cnt = params.serverParams.refText.replace(/^\s+|\s+$/g, '').split(' ').length;
                duration = 2030 + reftxt_cnt * 600;
            }else if(Utils.in_array(core_type, [ 'en.pred.exam'])){
                let reftxt_cnt = 0;
                if(params.serverParams.refText.hasOwnProperty('lm')){
                    reftxt_cnt = params.serverParams.refText.lm.match(/[a-zA-Z]+/ig).length
                }else{
                    reftxt_cnt = params.serverParams.refText.replace(/^\s+|\s+$/g, '').split(' ').length;
                }
                duration = 2030 + reftxt_cnt * 600;
            }
        }

        return duration;
    }

    /**
     * 停止录音接口
     *
     * flag = false 表示正常录音停止函数
     * falg == true 表示录音过程出现异常
     * */
    stopRecord(flag) {
        let that = this;
        if (this._status !== Utils.Status.recording){
            console.info("[stopRecord]: recorder not recording ! Please start recording");
            return;
        }
        this._resetStatus();
        //异常停止 连接已经断开 已返回错误信息不再向服务端发送停止命令  不再创建评分超时计时器
        if(!flag && this.wsContext[`ws${this._tokenId}`]){
            this.wsContext[`ws${this._tokenId}`].msgQue.push({cmd:"stop",data:null});
            this.wsContext[`ws${this._tokenId}`].sendAudio();
            this.wsContext[`ws${this._tokenId}`].timer_wait_res = setTimeout(this._setTimeOut(this.wsContext[`ws${this._tokenId}`]), this.__curr_record_param.serverParams.coreTimeout);
        }

        if(this.wsContext[`ws${this._tokenId}`]){
            this.wsContext[`ws${this._tokenId}`].connectStatus = Utils.ConnectStatus.waitResult;
        }

        if (this.__timer_stop) {
            clearTimeout(this.__timer_stop);
            this.__timer_stop = null;
        }

        this.record_worker.postMessage({
            command: 'exportAudio',
            type: this._audio_sampleRate.audioType
        });

        setTimeout(function () {
            that._recorder_frame({buffer: null, isLastFrame: true});
        },0);

        if(typeof this.__curr_record_param.onStop == "function"){
            this.__curr_record_param.onStop();
        }

        console.info("[stopRecord]: stopRecord success!!");

    }

    /**
     * SDK计算评分超时函数
     * 当在SDK设置时间（60s）未返回录音结果 表示录音评分超时，SDK主动断开连接并返回评分超时错误
     * */
    _setTimeOut(wsParams){
        const that = this;
        const self_tokenId = wsParams.tokenId;
        return (e)=>{
            that.wsContext[`ws${self_tokenId}`].connectStatus = Utils.ConnectStatus.connectEnd;
            if(that.wsContext[`ws${self_tokenId}`]){
                that.wsContext[`ws${self_tokenId}`].websocket.close();
                that.wsContext[`ws${self_tokenId}`].websocket = null;
                that.wsContext[`ws${self_tokenId}`].msgQue = [];
                delete that.wsContext[`ws${self_tokenId}`];
            }
           // if (!that.__curr_record_param) return;

          //  that.__curr_record_param.onStop(50201, 'core time out');

            if (typeof that.__curr_record_param.onScoreError == "function") {
                that.__curr_record_param.onScoreError({
                    id:"50201",
                    tokenId: self_tokenId,
                    message:"core time out"
                });
                console.error(`{id:"50201", tokenId: ${self_tokenId},message:"core time out"}`);
            } else {
                console.error('core timeout, and no onScoreError callback.');
            }
        }
    }

    /**
     * 重置录音引擎状态函数
     *
     * 当录音过程中出现异常抛出引擎错误 并重置引擎状态
     * */
    _resetStatus(){
        this._status = Utils.Status.completed;
    }

    /**
     * 开始回放最后一次录音接口。
     *
     * @param {Object} options - 回放时所需的参数。参数有：
     * @param {callback} options.onStop - 回放完成的Callback，格式：() => {}。
     * @param {callback} options.onStart - 回放开始的Callback，格式：() => {}。
     */
    startReplay(option) {
        this.__on_replay_stop = option.onStop;
        this.__on_replay_start = option.onStart;
        if (!this.__audio_replay&&this.__audio_replay.src) {
            console.error('no audio record!');
        } else {
           if(isNaN(this.__audio_replay.duration) || this.__audio_replay.duration <= 0){
                if (typeof  this.__on_replay_stop === "function"){
                    this.__on_replay_stop();
                    console.log("stop");
                } 
               return
           }
           
            this.__audio_replay.play();
            console.info("start replay!");
        }
    }

    /**
     *  停止回放接口
     *
     * */
    stopReplay() {
        if (this.__audio_replay) {
            this.__audio_replay.pause();
            console.info("stop replay!");
            this.__audio_replay.currentTime = 0;
            if (typeof this.__on_replay_stop == "function"){
                this.__on_replay_stop();
            }
        }
    }

    /**
     * 重置引擎接口
     * */
    reset(flag){
        this.stopRecord(flag);
        this.stopReplay();
        this._resetStatus();
        if(Object.keys(this.wsContext).length >=1){
                for(const k in this.wsContext){
                    clearTimeout(this.wsContext[k].timer_wait_res);
                    if(this.wsContext[k].websocket.readyState == WebSocket.OPEN){
                        this.wsContext[k].websocket.close();
                        this.wsContext[k].websocket = null;
                    }
                }
            this.wsContext = null;
            this.wsContext = {};
        }
        console.info("recorder reset success!")
    }


    _resetFlag(flag,tokenId){
        this.stopRecord(flag);
        let wsCon = this.wsContext[`ws${tokenId}`];
        wsCon.connectStatus = Utils.ConnectStatus.connectEnd;
        if(wsCon.timer_wait_res){
            clearTimeout(wsCon.timer_wait_res);
        }
        if(wsCon){
            wsCon.websocket.close();
            wsCon.websocket = null;
            wsCon = null;
            delete  this.wsContext[`ws${tokenId}`];
        }
        if(this.__timer_stop){
            clearTimeout(this.__timer_stop);
            this.__timer_stop = null;
        }
    }


    /**
     * 设置回放音量接口
     * @param {int} - 要设置的音量。 0 ~ 1
     */
    setVolume(volume){
        if(this.__audio_replay){
            this.__audio_replay.volume = volume;
        }else {
            this.options.onError({
                id:74001,
                message:"audio is null"
            });
            console.error("{id:74001, message:(setVolume) audio is null!}");
        }
    }

    /**
     * 获取回放音量接口
     * @result {int} - 录音机当前音量。 0 ~ 1
     */

    getVolume(){
        if(this.__audio_replay){
            return this.__audio_replay.volume;
        }else {
            this.options.onError({
                id:74001,
                message:"audio is null"
            });
            console.error("{id:74001, message:(getVolume) audio is null!}");
            return  0.0;
        }
    }

    /**
     * 设置录音机录音音量接口
     * @param {int} volume - 要设置的音量。 0 ~ 1
     */
    setMicVolume(volume){
        if(this.inputPoint){
            this.inputPoint.gain.value = volume;
        }else {
            this.options.onError({
                id:74002,
                message:"context or gain is null"
            });
            console.error("{id:74002, message:(setMicVolume) audioContext or gain is null!}");
        }
    }

    /**
     * 获取录音机录音音量接口
     * @result {int} - 录音机当前音量。 0 ~ 1
     */

    getMicVolume(){
        if(this.inputPoint){
            return  this.inputPoint.gain.value;
        }else {
            this.options.onError({
                id:74002,
                message:"context or gain is null"
            });
            console.error("{id:74002, message:(getMicVolume) audioContext or gain is null!}");
            return  0.0;
        }
    }

    /**
     * 显示波形图接口
     *
     *  params {function} - 返回实时录音图谱回调
     * */
    showVolumeBar(callback) {
        if(typeof callback !== "function"){
            this.__volumebar.show();
        }else{
            this.__volumebar.getFrequencyData(callback);
        }

    }

    __onScoreTimeout() {
        if (!this.__curr_record_param) return;

        this.__curr_record_param.onStop(50100, 'core time out');

        if (this.__curr_record_param.onScoreError) {
            this.__curr_record_param.onScoreError.call(50100);
        } else {
            console.error('core timeout, and no onScoreError callback.');
        }
    }

    /**
     *  校验参数是否合法函数
     *  @return{boolean} - true表示参数非法
     */

    _isOptions(options){
        let isInvalid = false;
        const allow_alg = ['sha1','sha256','md5'];
        if(!options.appKey || options.appKey.length == 0){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75001,
                    message:`appKey is required and cannot be null!`
                });
            }
            console.error(`{id:75001, message:appKey is required and cannot be null! }`);
            isInvalid = true
        }else if(typeof options.appKey !== "string"){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75001,
                    message:`invalid appKey,appKey:${options.appKey}`
                });
            }
            console.error(`{id:75001, message:invalid appKey,appKey:${options.appKey} }`);
            isInvalid = true
        }

        if((!options.sigurl || options.sigurl.length == 0 )&& typeof options.signature !== "function"){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75002,
                    message:`sigurl is required and cannot be null`
                });
            }
            console.error(`{id:75002,message:sigurl is required and cannot be null!}`);
            isInvalid = true
        }else if((!options.sigurl  || typeof options.sigurl !== "string" || options.sigurl.length == 0 ) && typeof options.signature !== "function"){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75002,
                    message:`invalid sigurl,sigurl:${options.sigurl}`
                });
            }
            console.error(`{id:75002,message:invalid sigurl,sigurl:${options.sigurl}}`);
            isInvalid = true
        }


        if(!options.server || options.server.length == 0){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75003,
                    message:`server is required and cannot be null`
                });
            }
            console.error(`{id:75003, message:server is required and cannot be null}`);
            isInvalid = true
        }else if( typeof options.server !== "string" ){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75003,
                    message:`invalid server,server:${options.server}`
                });
            }
            console.error(`{id:75003, message:invalid server,server:${options.server}}`);
            isInvalid = true
        }

        if(allow_alg.indexOf(options.alg) < 0){
            if(typeof options.onError == "function"){
                options.onError({
                    id:75003,
                    message:`invalid alg,alg:${options.alg},Allowed alg type ['sha1','sha256','md5']`
                });
            }
            console.error(`{id:75004, message:invalid alg,alg:${options.alg},Allowed alg type ['sha1','sha256','md5']}`);
            isInvalid = true
        }
        return  isInvalid
    }

    /*
    * 销毁录音机引擎接口
    * */
    dispose(){
        if(this.context){
            this.context.close();
            this.context = null;
            this.inputPoint = null;
            this.analyserNode = null;
            this.audioInput = null;
        }
    }

    /**
     * 向logBus服务发送websocket连接日志函数
     *
     * */
    __log(logmes) {
        let logbus = typeof logmes.logbus === "boolean" ? logmes.logbus : true;
        if(!logbus){ return }
        let uid = (this.options.data && this.options.data.serverParams && this.options.data.serverParams.userId) ? this.options.data.serverParams.userId : 'unknown';
        let url = `${Config.logserver}?eid=${logmes.evtid}&est=${logmes.statusid}&applicationId=${this.options.appKey}&uid=${uid}`;
        let params = {
            conn_url: encodeURIComponent(logmes.serverurl),
            reason: logmes.reason,
            user_agent: navigator.userAgent,
            prot: 1
        };

        let xreq = new XMLHttpRequest();

        xreq.open('POST', url, true);
        xreq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xreq.send('log=' + JSON.stringify(params));
    }
    /**
     * 检验签名数据是否合法
     *
     * @return{boolean} - true 签名参数合法
     * */
    _isInvalidSigData(data,options){
        if(Object.prototype.toString.call(data) !== "[object Object]"){
            console.error("invalid sig data ;sig:" +  JSON.stringify(data));
            if(typeof options.onError == "function"){
                options.onError({
                    id:75005,
                    message:`invalid sig data:${JSON.stringify(data)}`
                });
            }
            return false;
        }

        if(!data.timestamp || typeof data.timestamp !== "string"){
            console.error("invalid sig.timestamp ; sig:" +  JSON.stringify(data));
            if(typeof options.onError == "function"){
                options.onError({
                    id:75005,
                    message:`invalid sig.timestamp:${JSON.stringify(data)}`
                });
            }
            return false;
        }
        if(!data.sig || typeof data.sig !== "string"){
            console.error("invalid sig.sig ; sig:" +  JSON.stringify(data));
            if(typeof options.onError == "function"){
                options.onError({
                    id:75005,
                    message:`invalid sig.sig:${JSON.stringify(data)}`
                });
            }
            return false;
        }

        return true;
    }

    test_connection() {
        let onConnectorStatusChange = this.options.onConnectorStatusChange;
        onConnectorStatusChange(50109);

        let ws = null;

        try {
            ws = new WebSocket(`${this.options.server}/en.sent.score`);
        } catch (err) {
            onConnectorStatusChange(50101);
            return;
        }

        ws.onopen = (e) => {
            onConnectorStatusChange(50100);
            ws.close;
        };
        ws.onerror = (e) => {
            onConnectorStatusChange(50101);
        };
    }
}

window.Html5Recorder = Html5Recorder;
export default Html5Recorder

